#include "lib.h"
#include "process.h"
#include "waitqueue.h"
#include "kmem.h"
#include "serial.h"
#include "mmu.h"
#include "mmu_asm.h"
 

/// User- und Kernelstackgröße für Start eines Prozesses
#define STACK_SIZE 4096
#define PROCESS_BLOCK 2816
/**
 * Assembler dummyroutine, die beim Laden eines Prozesses eingetragen wird.
 * INIT_JUMP_PROCESS kehrt beim Aufruf direkt aus dem Systemmodus zurück 
 * und startet so Prozess.
 */
void INIT_JUMP_PROCESS();

process_t *__current_process;
static int process_id_seed;

/**
 * Alloziiert und initialisiert Speicher für Prozessverwaltunsblock
 */
static process_t *alloc_process();

/**
 * Gibt Prozessverwaltungsblock wieder frei
 */
static void free_process(process_t *process);

/**
 * Initialisiert Stack des Prozesses mit Argumenten
 */
static void *init_user_stack(void* user_stack, char* args, int *nargs);

/**
 * Initialisiere Prozessverwaltungsblock mit Prozessroutine und lade
 * Prozessargumente auf Stack
 */
static bool init_process( process_t *process, void* process_start, char* args);

/**
 * Initialisiert Prozesszustand, so dass Prozess beim nächsten Prozesswechsel
 * automatisch startet
 */
static void setup_cpu_state(process_t *process, void *process_start, 
                            void *kernel_stack, void *user_stack, int nargs);

/**
 i Starte initialisierten Prozess
 */
static bool start_process( process_t *process );

/**
 * Wählt den nächsten Prozess für Kontextwechsel aus.
 */
static process_t *scheduler(process_t *current);

/**
 * Führt Kontextwechsel aus
 */
static void swap_process(process_t *current, process_t *next);

/**
 * Assemblerroutine, die jeweiligen Prozesszustand über Kernelstacks austauscht.
 */
void switch_cpu(cpu_state_t **from, cpu_state_t **to);





process_t *
current_process()
{
    return __current_process;
}
    
bool
load_process( void* process_start, char* args )
{       
    write("Starte Prozessladung\n");
    process_t *process = alloc_process();
    if(!process) if(process){ //TODO Nachfragen ob alle Tassen im Schrank
      free_process( process );
      return false;
    }
    if(!init_process( process, process_start, args)) {
      if(process) free_process( process );
      return false;
    }
	
    if(!start_process( process )) {
      if(process) free_process( process );
      return false;
    }
 write("Starte Prozessladung\n");
    return true;
}

static process_t *
alloc_process()
{
    process_t *process = kmalloc(sizeof(process_t));
    process->state = PROCESS_EXISTENT;
    process->process_id = process_id_seed++;
    list_node_init(&process->process_node);

    //Hole Speicher fuer Stacks
    //process->process_mem.user_stack.start = kmalloc( STACK_SIZE );
    //Setze den Stack an das Ende des 1Mb großen Datenblockes jeden Prozesses, Startwert des Blockes 0xB0000000, Ende: 0xB00FFFFF
    process->process_mem.user_stack.start = 0xB00FFFFF-STACK_SIZE;
    process->process_mem.user_stack.size = STACK_SIZE;
    //Hole 1Mb Speicher für Prozess
  
    unsigned long tmp_physical_address =  ((unsigned long)l1_address + ((1<<20) - ((unsigned long)l1_address % (1<<20)))) + (process->process_id-1)*(1<<20);
    process->process_mem.physical_address.start = (void *)tmp_physical_address; 
    process->process_mem.physical_address.size = (unsigned long) (1<<20);
    

    if(!((long)tmp_physical_address % (1 << 20))){
	kprintf("1Mb-ausgerichtet! Adresse: %x\n", tmp_physical_address);
    }
    else{
	kprintf("Prozessspeicher falsch reserviert (%x), breche ab!\n",tmp_physical_address);
	return NULL;
    }
	    
    //lösche alte physikalische Adresse
    kprintf("Lösche alte physikalische Adresse %x\n", l1_address[PROCESS_BLOCK]);
    l1_address[PROCESS_BLOCK] = l1_address[PROCESS_BLOCK] & ((1<<20)-1);
    
    //setze physikalische Adresse des neuen Prozesses
    
    l1_address[PROCESS_BLOCK] = (long*)((long)l1_address[PROCESS_BLOCK] | (long)process->process_mem.physical_address.start);
    kprintf("Setze neue physikalische Adresse %x\n", l1_address[PROCESS_BLOCK]);
    flush_tlb();
    
    //error 1
    if(!process->process_mem.user_stack.start) {
      kfree(process, sizeof(process_t));
      return NULL;
    }
    
    process->process_mem.kernel_stack.start = kmalloc( STACK_SIZE );
    process->process_mem.kernel_stack.size = STACK_SIZE;
    //error 2
    if(!process->process_mem.kernel_stack.start) kfree(process->process_mem.user_stack.start, STACK_SIZE);
    
    return process;
}

static void
free_process(process_t *process)
{
    if(process) kfree( process, sizeof(process_t) );
}

static void 
*init_user_stack(void* user_stack, char* args, int *nargs)
{       
    int i;
    char *stack_args;
    *nargs = 0;       

    //copy arguments into user memory
    user_stack = (void*)( (unsigned long)user_stack - (strlen(args) + 1));
	kprintf("userstack = %x\n",user_stack);
    strcpy(user_stack, args);
    kprintf("lade Stackdaten\n");
    stack_args = user_stack;        //save pointer to arguments 

    //Allignment des Stackpointers anpassen
    while( (unsigned long)user_stack & 3) {
        user_stack = (void*) ( PTR_ADDR(user_stack) &~ 3 );
    }   


    //Trenne Argumente bei Leerzeichen
    for(i = strlen(stack_args) - 1;i >= 0; i--) {
        if( stack_args[i] == ' ') stack_args[i] = 0; //do split argument

        //if new arg, put pointer onto stack and increase number of arguments
        if(i==0 || (stack_args[i] != 0 && stack_args[i-1] == ' ')) {
            (*nargs)++;
            user_stack = (void*) (PTR_ADDR(user_stack) - sizeof(char*));
            *(char**)user_stack = &stack_args[i];
        }
    }
    return user_stack;
}

static bool
init_process( process_t *process, void* process_start, char* args)
{
    //bestimme Adresse fuer Stackpointer

    void *kernel_stack = 
            ((unsigned char*)process->process_mem.kernel_stack.start) 
            + process->process_mem.kernel_stack.size - 8;
    void *user_stack = ((unsigned char*)process->process_mem.user_stack.start) 
                       + process->process_mem.user_stack.size - 4;//ganz klar
    int nargs;
    
    user_stack = init_user_stack( user_stack, args, &nargs );


    setup_cpu_state( process, process_start, kernel_stack, user_stack, nargs );
  return true;
}

static void
setup_cpu_state(process_t *process, void *process_start, 
                void *kernel_stack, void *user_stack, int nargs)
{

    *((unsigned long*)kernel_stack - 1) = (unsigned long)process_start;
    cpu_state_t *cpu_state = (cpu_state_t*)(PTR_ADDR(kernel_stack) 
                             - sizeof(cpu_state_t) - 4);

    memset(cpu_state, 0, sizeof(cpu_state_t));
    cpu_state->r14 = 0;
    cpu_state->r13 = (register_t)user_stack;
    cpu_state->pc = (register_t)INIT_JUMP_PROCESS;
    kprintf("Starte Prozess im Usermode und erlaube interrupts\n");
    cpu_state->spsr = 0x50; //Starte Prozess im Usermode und erlaube interrupts
    cpu_state->r0   = (register_t)nargs;
    cpu_state->r1   = (register_t)user_stack;
    
  
    process->cpu_state = cpu_state;
}

static bool 
start_process( process_t *process )
{   
    process->state = PROCESS_READY;
    wq_push( &active_list, process );
    return TRUE;
}  

void 
reschedule()
{
    process_t *process = current_process();
    process_t *next_process = scheduler(process);
    if(process != next_process){
        swap_process(process, next_process);
    }
}

static process_t *
scheduler(process_t *current)
{
    //Schiebe aktuellen Prozess in active_list
    if(current && current->state == PROCESS_RUNNING){
        wq_enqueue( &active_list, current);
    }

    //bestimme naechsten Prozess
    process_t *next = wq_pop( &active_list );

    if(next == NULL) while(1);
    return next;
}

static void swap_process(process_t *current, process_t *next)
{
	
    //save shared resources and set state
    if(current){
        current->state = PROCESS_READY;
    }
    
    //lösche alte physikalische Adresse
    kprintf("Lösche alte physikalische Adresse %x\n", l1_address[PROCESS_BLOCK]);
    l1_address[PROCESS_BLOCK] = l1_address[PROCESS_BLOCK] & ((1<<20)-1);
    
    //setze physikalische Adresse des neuen Prozesses
    
    l1_address[PROCESS_BLOCK] = (long*)((long)l1_address[PROCESS_BLOCK] | (long)next->process_mem.physical_address.start);
    kprintf("Setze neue physikalische Adresse %x\n", l1_address[PROCESS_BLOCK]);
    
    //restore shared resource of next
    next->state = PROCESS_RUNNING;
    next->time_slice = 1;
    __current_process = next;
    
    flush_tlb();
    switch_cpu( current ? &current->cpu_state : NULL, &next->cpu_state);

}
        

bool 
process_init()
{
    __current_process = NULL;
    process_id_seed = 1;
    wq_init( &active_list );


    return true;
}

